package externalflame.systemdesign.comment.service;

import externalflame.systemdesign.comment.dto.CommentDto;
import externalflame.systemdesign.comment.entity.Comment;
import externalflame.systemdesign.comment.entity.Vote;
import externalflame.systemdesign.comment.exception.AlreadyVoteException;
import externalflame.systemdesign.comment.utils.ActionEnum;
import externalflame.systemdesign.comment.utils.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;


@Service
@Component
public class CommentServiceImpl implements CommentService {

    @Resource
    MongoTemplate mongoTemplate;

    @Resource
    RedisTemplate<String, HashSet<String>> redisTemplate;

    private String toRedisVoteKey(String commentId, ActionEnum actionEnum) {
        return String.format("comment:%s:votes:%s", commentId, actionEnum);
    }

    private void filterVoteRequests(String userId, String commentId, ActionEnum actionEnum) {
        if (redisTemplate.opsForSet().isMember(toRedisVoteKey(commentId, actionEnum), userId))
            throw new AlreadyVoteException();
    }

    private void cacheVote(String userId, String commentId, ActionEnum actionEnum) {
        redisTemplate.opsForSet().union(toRedisVoteKey(commentId, actionEnum), userId);
    }

    public Comment addComment(String userId, CommentDto commentDto) {
        // TODO: mongo4.0 之后，已经可以使用 事务了，在 Spring 里可以用吗？怎么用？
        Comment comment = new Comment();
        String id = UUID.randomUUID().toString();
        comment.setId(id);
        comment.setAuthor(userId);
        comment.setContent(commentDto.getContent());
        comment.setCreatedAt(new Date());
        comment.setUpVote(0L);
        comment.setDownVote(0L);
        comment.setReplyTo(commentDto.getReplyTo());
        comment.setIsValid(true);
        comment.setContextId(commentDto.getContextId());
        mongoTemplate.insert(comment, "comment");
        return mongoTemplate.findById(id, Comment.class, "comment");
    }

    public Boolean deleteComment(String commentId) {
        mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(commentId)), new Update().set("isValid", false), "comment");
        return true;
    }

    public Page<Comment> getCommentByContextId(String contextId, int page, int pageSize) {
        Comment comment = new Comment();
        comment.setContextId(contextId);
        comment.setIsValid(true);
        comment.setReplyTo(0L);
        Query query = new Query();
        query.addCriteria(Criteria.where("contextId").is(contextId));
        query.addCriteria(Criteria.where("isValid").is(true));
        query.addCriteria(Criteria.where("replyTo").is(0));
        query.with(Sort.by("createdAt"));
        query.skip((page - 1) * pageSize);
        query.limit(pageSize);

        List<Comment> ans = mongoTemplate.find(query, Comment.class, "comment");
        Long totalSize = mongoTemplate.count(query, Comment.class, "comment");
        Page<Comment> result = new Page<>();
        result.setData(ans);
        result.setPageSize(pageSize);
        result.setPage(page);
        result.setTotalPages((int) ((totalSize + pageSize - 1) / pageSize));
        return result;
    }

    // mongodb 事务，必须是集群模式才能启动，
    // @Transactional
    public String vote(String userId, String commentId, ActionEnum actionEnum) throws AlreadyVoteException {
        filterVoteRequests(userId, commentId, actionEnum);

        // 插入数据到 vote 文档集。
        Vote vote = new Vote();
        vote.setAction(actionEnum);
        vote.setCommentId(commentId);
        vote.setUserId(userId);
        mongoTemplate.insert(vote, "vote");

        // 更新点赞/点踩数。
        Query query = new Query(Criteria.where("_id").is(commentId));
        Update numUp;
        if (actionEnum == ActionEnum.upVote) {
            numUp = new Update().inc("upVote", 1);
        } else {
            numUp = new Update().inc("downVote", 1);
        }
        mongoTemplate.updateFirst(query, numUp, "comment");
        cacheVote(userId, commentId, actionEnum);
        return "success";
    }

    @Override
    public Page<Comment> getRepliesByCommentId(String commentId, int page, int pageSize) {
        Query query = new Query();
        query.addCriteria(Criteria.where("replyTo").is(commentId));
        query.addCriteria(Criteria.where("isValid").is(true));
        query.with(Sort.by("createdAt"));
        query.skip((page-1) * pageSize);
        query.limit(pageSize);

        List<Comment> commentList = mongoTemplate.find(query, Comment.class, "comment");
        Long totalPages = mongoTemplate.count(query, Comment.class, "comment");

        Page<Comment> ans = new Page<>();
        ans.setData(commentList);
        ans.setPage(page);
        ans.setPageSize(pageSize);
        ans.setTotalPages((int) ((totalPages + pageSize - 1) / pageSize));
        return ans;
    }
}
